import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:gameplay_flutter/lib_runtime/utils/num_util.dart';
import '../../lib_basemodel/chat_room/chat/chat_content.dart';
import '../../lib_basemodel/chat_room/chat/hb_message_type.dart';
import '../../lib_basemodel/chat_room/chat/message.dart';
import '../../lib_basemodel/chat_room/chat/message_data.dart';
import '../../lib_basemodel/chat_room/socket/req_content.dart';
import '../../lib_runtime/logger.dart';

/// socket，连接、重连、解析、处理socket消息
mixin JRSocket {
  static const String tag = 'WebSocket -->';
  static const int _socketPingInterval = 5;
  static const int _retryMaxTime = 30 * 1000; // 重试总时间

  final Map<int, ValueChanged<ReqContent?>> _callbacks = {};
  final Set<SocketMsgHandler> _handlers = {};

  String get socketUrl;

  Future<Map<String, dynamic>> get socketParams;

  String get token;

  Connectivity? _conn;

  WebSocket? _socket;
  bool _connecting = false;
  int _reqId = 0;
  int _timeDiff = 0;
  Timer? _pingTimer;
  bool _disconnected = false;
  int _cmdSeq = -1; // cmd消息序列号
  var _refreshToken = false;

  onRetryMax([String? msg]) {}

  onReconnected() {}

  onConnected() {}

  void connect() async {
    log('JRSocket connect with  socketUrl: $socketUrl, params: $socketParams, _connecting = $_connecting, _socket: $_socket, _disconnected: $_disconnected');
    if (_socket != null || _disconnected || _connecting) return;

    _conn ??= Connectivity();

    _connecting = true;
    try {
      _socket = await WebSocket.connect(socketUrl, headers: await socketParams);
      _socket?.pingInterval = const Duration(seconds: _socketPingInterval);
    } catch (e) {
      log('Socket connect with exception: $e, retry...');
      _reconnect();
      return;
    } finally {
      _connecting = false;
    }

    if (_disconnected) {
      _socket?.close();
      return;
    }

    _socket?.listen((dynamic message) async {
      log('Receive message with _errorTime: $_errorTime');
      if (_errorTime > 0) onReconnected();

      _errorTime = 0;
      _isTrying = false;
      _onReceive(message);
    }, onDone: () {
      log('WebSocket: $socketUrl onDone, retry...');
      _reconnect();
    }, onError: (e) {
      log('WebSocket: $socketUrl retry with error: $e');
      _reconnect();
    }, cancelOnError: true);

    log('WebSocket $socketUrl connected ---------');
    _schedulePing();
    onConnected();
  }

  void _schedulePing() {
    _pingTimer?.cancel();
    _pingTimer =
        Timer.periodic(const Duration(seconds: _socketPingInterval), _sendPing);
  }

/* 后台定义的消息
  HEART_BEAT_MSG(1, "心跳消息"),
  USER_ID_MSG(2, "用户id消息"),
  CHAT_ROOM_OPEN_MSG(3, "聊天室公屏消息"),
  VOICE_AUDIT(4, "语音流检测"),
  MSG_REPLY(5, "消息回执"),   // 用于统计全服广播消息触达率
  LOGOUT(6, "用户登出"),
  REVERSE_CTRL(999, "逆向控制"),
*/
  void _onReceive(dynamic message) {
    log('WebSocket $socketUrl receive msg: $message');
    try {
      var dataMap = json.decode(message);

      print('收到的消息:$dataMap');

      if (dataMap['type'] == 1) {
        handlePingMsg(dataMap);
      } else if (dataMap['type'] == 2) {
      } else if (dataMap['type'] == 3) {
        handleMsg(dataMap);
      } else if (dataMap['type'] == 4) {
        print('音流检测');
      } else if (dataMap['type'] == 5) {
        print('消息回执');
        handleMsg(dataMap);
      } else if (dataMap['type'] == 6) {
        print('用户登出');
      } else if (dataMap['type'] == 999) {
        print('逆向控制');
      } else {
        print('没有对应的socket消息');
      }
    } catch (e) {
      log('WebSocket handle msg with exception: $e');
    }
  }

  int _errorTime = 0;
  bool _isTrying = false;
  void _reconnect() async {
    log('Reconnect with _isTrying: $_isTrying, _disconnected: $_disconnected, _errorTime: $_errorTime');
    if (_isTrying || _disconnected) return;

    _pingTimer?.cancel();
    await _closeSocket();

    if (_errorTime >= _retryMaxTime) {
      log('Quit with retry max, _errorTime: $_errorTime, _retryMaxTime: $_retryMaxTime');
      onRetryMax();
      _isTrying = false;
      return;
    }

    List<ConnectivityResult>? result = await _conn?.checkConnectivity();
    int delayTime = result == ConnectivityResult.none ? 5000 : 2000;
    _errorTime = _errorTime + delayTime;
    _isTrying = true;
    await Future.delayed(Duration(milliseconds: delayTime), () {
      _isTrying = false;
      if (_socket == null && socketUrl.isNotEmpty && !_disconnected) {
        connect();
      }
    });
  }

  Future _closeSocket() async {
    log('_close socket, _socket: $_socket');
    if (_socket == null) return;

    await _socket?.close();
    _socket = null;
  }

  _sendPing(Timer timer) {
    if (!timer.isActive) return;

    if (_refreshToken) {
      var pingData = {
        'type': 1,
        'msg': 'heartbeat',
      };
      _send(jsonEncode(pingData));
      _refreshToken = false;
    }

    Map heartbeatMap = {
      'type': 1,
      'msg': 'heartbeat',
    };
    _send(jsonEncode(heartbeatMap));
  }

  void _send(String msg) {
    if (_socket?.readyState == WebSocket.open) {
      try {
        _socket?.add(msg);
        log('ping: $msg');
      } catch (e) {
        log('ping: $msg, error: $e');
      }
    }
  }

  /// 发送公屏消息
  void sendChatMsg(String jsonStr) {
    return _send(jsonStr);
  }

  /// 发送socket请求消息
  // Future<ReqContent> sendReqMsg(ReqContent content) {
  //   _reqId++;
  //   var msgData = MessageData()
  //     ..reqId = _reqId
  //     ..content = jsonEncode(content);
  //   var msg = Message()
  //     ..type = MessageTypeEnum.socket
  //     ..data = msgData;

  //   _send(_encodeMsg(msg));
  //   var completer = Completer<ReqContent>();
  //   _callbacks[_reqId] = (data) => completer.complete(data);
  //   return completer.future;
  // }

  String _encodeMsg(Message msg) {
    var msgMap = msg;
    // if (msgMap is Map) {
    //   var msgContent = msgMap['data']['content'];
    //   // if (msgContent is Map) msgContent.remove('@type');
    // }
    return jsonEncode(msgMap);
  }

  // String _encodeMapMsg(Map msg) {
  //   var msgMap = msg;
  //   // if (msgMap is Map) {
  //   //   var msgContent = msgMap['data']['content'];
  //   //   // if (msgContent is Map) msgContent.remove('@type');
  //   // }
  //   return jsonEncode(msgMap);
  // }

  void disconnect() {
    _pingTimer?.cancel();
    _pingTimer = null;

    _handlers.clear();
    if (_socket != null) {
      if (_socket!.readyState == WebSocket.open) {
        _socket!.close(WebSocketStatus.goingAway);
      }
      _socket = null;
    }
    _disconnected = true;
  }

  bool addHandler(SocketMsgHandler handler) => _handlers.add(handler);

  bool rmHandler(SocketMsgHandler handler) => _handlers.remove(handler);

  void rmHandlers() => _handlers.clear();

  // Message _decodeMsg(dynamic message) => Message.create()
  //   ..mergeFromProto3Json(message,
  //       typeRegistry: typeRegistry, ignoreUnknownFields: true);

  // Message _decodeMsg(dynamic message) => Message.create()
  //   ..mergeFromProto3Json(message,
  //       typeRegistry: typeRegistry, ignoreUnknownFields: true);

  Future<void> handleMsg(ChatContent msg) async {
    var type = msg.type;
    // switch (type) {
    // case MessageTypeEnum.ping:
    //   break;
    // case MessageTypeEnum.socket:
    //   break;
    // case MessageTypeEnum.cmd:
    //   break;
    // case MessageTypeEnum.text:
    //   break;
    // }

    if (type == HBMessageType.chatRoomOpenMsg || type == HBMessageType.gift) {
      // handleReqMsg(msg.data?.reqId ?? -1, msg.data);
      handleChatMsg(msg);
    }

    // else if (type == MessageTypeEnum.text) {
    // }
    else {
      // handleCmdMsg(msg);
      log('handleMsg: no msg type');
    }
  }

  Future<void> handlePingMsg(Map msg) async {
    var code = NumUtil.parseInt(msg['code']);
    var message = msg['message'];
    var time = NumUtil.parseInt(msg['time']);

    if (code > 300) {
      // code > 300，处理ping的信令

      // code 301，需要通知长连接更新token
      if (code == 301) _refreshToken = true;
    } else if (code != 0) {
      // code非0，断开连接

      disconnect();
      onRetryMax(message);
    }

    _timeDiff = time - DateTime.now().millisecondsSinceEpoch;
    // GlobalConfig.timeDiff = _timeDiff;
  }

  Future<void> handleReqMsg(int reqId, MessageData? data) async {
    if (data != null) _callbacks.remove(reqId)?.call(data.reqContent);
  }

  // Future<void> handleCmdMsg(Message msg) async {
  //   // Scene scene = msg.data.scene;
  //   int seq = msg.data?.seq ?? 0;
  //   // if (msg.type == MessageTypeEnum.cmd && seq < _cmdSeq) {
  //   //   log('Receive deprecated cmd msg with id: ${msg.id}, seq: $seq, currentSeq: $_cmdSeq, ignore!!!');
  //   //   return;
  //   // }

  //   _cmdSeq = seq;
  //   _handlers
  //       // .where((_) =>
  //       //     _.msgType == MessageTypeEnum.cmd && _.sceneList.contains(scene))
  //       .forEach((_) => _.handleMsg(msg));
  // }

  void handleChatMsg(ChatContent msg) {
    _handlers
        .where((_) =>
            msg.type == HBMessageType.chatRoomOpenMsg ||
            msg.type == HBMessageType.gift)
        .forEach((_) => _.handleMsg(msg));
  }

  int get serverTime => DateTime.now().millisecondsSinceEpoch + _timeDiff;

  void log(dynamic msg) => RLog.d('$tag $msg');

  // static TypeRegistry typeRegistry = TypeRegistry([
  //   ChatContent.getDefault(),
  //   ReqContent.getDefault(),
  //   RoomUserJoin.getDefault(),
  //   // RoomUserLeave.getDefault(),
  //   RoomSendGift.getDefault(),
  //   RoomManage.getDefault(),
  //   // RoomShare.getDefault(),
  //   RoomMicChange.getDefault(),
  //   RoomMetaDataChange.getDefault(),
  //   RoomRealTimeRankChange.getDefault(),
  //   RoomGiftValueChange.getDefault(),
  //   RoomMicQueueChange.getDefault(),
  //   RoomVipParkChange.getDefault(),
  //   RoomRoleChange.getDefault(),
  //   RoomProgramChange.getDefault(),
  //   // RoomKickUser.getDefault(),
  //   RidePush.getDefault(),
  //   RoomFlyingScreen.getDefault(),
  //   ChatContent.getDefault(),
  //   CardChatContent.getDefault(),
  //   TacitPush.getDefault(),
  //   RoomOffMicChange.getDefault(),
  //   RoomMicShowImg.getDefault(),
  //   RoomSudGameChange.getDefault(),
  //   CPInfo.getDefault(),
  //   CpPairChange.getDefault(),
  //   CPHandsHeartChangeInfo.getDefault(),
  //   RoomEffect.getDefault()
  // ]);
}

mixin SocketMsgHandler {
  // MessageTypeEnum get msgType => MessageTypeEnum.cmd;
  // Set<Scene> get sceneList => {};
  handleMsg(ChatContent msg) {}

  void log(dynamic msg) => RLog.d(msg);
}
